<?php
/** 
 * Copyright (c) 2012, Jiehun.com.cn Inc. All Rights Reserved
 * @author dengxiaolong@jiehun.com.cn
 * @date 2012-12-25
 * @version 1.0 
 * @brief 
 *  
 **/

final class HapTemplate
{
	const LEFT_SPLITTER = '{{';
	const RIGHT_SPLITTER = '}}';
	
	const TYPE_ROOT = 0;
	const TYPE_STRING = 1;
	const TYPE_VARIABLE = 8;
	const TYPE_TAG = 256;
	const TYPE_SUB_TAG = 512;
	
	const CHILDREAN_KEY = '__children__';
	
	private $v;
	
	private static $supportedTags = array(
		'switch' => FALSE,
		'repeator' => FALSE,
		'empty' => FALSE,
		'notempty' => FALSE,
		'cycle' => FALSE,
		'case'  => array('switch'),
		'default' => array('switch'),
		'include' => FALSE,
	);
	
	private $_root;
	
	private $_leftSpliterLen = 1;
	private $_rightSpliterLen = 1;
	
	function __construct()
	{
		$this->_leftSpliterLen = strlen(self::LEFT_SPLITTER);
		$this->_rightSpliterLen = strlen(self::RIGHT_SPLITTER);
	}
	
	/**
	 * 注册支持的标签
	 * @param string $tagName
	 * @param array $subTags
	 * @throws Exception
	 */
	public static function registerSupportTag($tagName, $subTags = array())
	{
		if (isset(self::$supportedTags[$tagName])) {
			throw new Exception('haptemplate.u_tagExisted');
		}
		
		$classNames = array('HapTemplate_Tag_'.ucfirst($tagName));
		foreach($subTags as $sTag) {
			$classNames[] = 'HapTemplate_Tag_'.ucfirst($tagName).'_'.ucfirst($sTag);
		}
		
		foreach($classNames as $className) {
			if (!class_exists($className)) {
				throw new Exception('haptemplate.u_classNotExisted class='.$className);
			}
		}

		self::$supportedTags[$tagName] = FALSE;
		foreach($subTags as $sTag) {
			if (!isset(self::$supportedTags[$sTag])) {
				self::$supportedTags[$sTag] = array();
			}
			self::$supportedTags[$sTag][] = $tagName;
		}
	}
	
	function compile($content)
	{
		$this->_root = new HapTemplate_Tag_Root($content);
		$this->buildCompiledTree($this->_root);
		
		return $this->_root->__toString();
	}
	
	/**
	 * 输出执行结果
	 * @param string $content
	 */
	public function output($content)
	{
		ob_start();
		eval('?' . '>' . trim($content));

		$content = ob_get_contents();
		ob_end_clean();

		return $content;
	}
	
	/**
	 * 分配变量
	 * @param string $key
	 * @param mixed $value
	 * @return Template
	 */
	public function assign($key, $value = NULL)
	{
		if (!is_array($key)) {
			$key = array($key => $value);
		}
		foreach($key as $k => $v) {
			$this->v[$k] = $v;
		}
		return $this;
	}
	
	/**
	 * 
	 */
	private function buildCompiledTree($tree)
	{
		$content = $tree->getContent();
		while($content) {
			// 找到第一个标签
			$leftPos = mb_strpos($content, self::LEFT_SPLITTER);
			$rightPos = FALSE;
			if ($leftPos !== FALSE) {
				$rightPos = mb_strpos($content, self::RIGHT_SPLITTER, $leftPos + $this->_leftSpliterLen);
			}
			if ($leftPos === FALSE || $rightPos === FALSE) {
				return $tree->addChild(new HapTemplate_Tag_String($content));
			}
			
			// 标签主体内容
			$body = NULL;
			if ($rightPos !== FALSE && mb_substr($content, $rightPos - 1, 1) == '/') {
				$body = '';
			}
			
			// 寻找第一个标签
			$tagStr = trim(mb_substr($content, $leftPos + $this->_leftSpliterLen, $rightPos - ($leftPos + $this->_leftSpliterLen)));
			$tagEndPos = mb_strpos($tagStr, ' ');
			$propStr = '';
			if ($tagEndPos === FALSE) {
				$tag = $tagStr;
				$propStr = '';
			} else {
				$tag = mb_substr($tagStr, 0, $tagEndPos);
				$propStr = trim(mb_substr($tagStr, $tagEndPos + 1));
			}
			
			if ($leftPos !== 0) {
				$tree->addChild(new HapTemplate_Tag_String(mb_substr($content, 0, $leftPos)));
			}
			
			if (!isset(self::$supportedTags[$tag])) {
				throw new Exception('haptemplate.u_tagNotSupported tag='.$tag);
			}
			
			if (self::$supportedTags[$tag] !== FALSE) {
				if ( !in_array($tree->tag, self::$supportedTags[$tag]) ) {
					throw new Exception('haptemplate.u_illegalChildTag ptag='.$tree->tag.' ctag='.$tag);
				}
				$className = 'HapTemplate_Tag_'.ucfirst($tree->tag).'_'.ucfirst($tag);
			} else {
				$className = 'HapTemplate_Tag_'.ucfirst($tag);
			}
			
			// 寻找标签结尾
			if ($body === NULL) {
				$endTag = self::LEFT_SPLITTER.'/'.$tag.self::RIGHT_SPLITTER;
				$endTagPos = mb_strpos($content, $endTag, $rightPos + 1);
				if ($endTagPos === FALSE) {
					throw new Exception('haptemplate.u_tagNotClosed tag='.$tag);
				}
				$body = mb_substr($content, $rightPos + $this->_rightSpliterLen, $endTagPos - ($rightPos + $this->_rightSpliterLen));
			} else {
				$endTag = '';
				$endTagPos = $rightPos;
			}
			if (!class_exists($className)) {
				throw new Exception('hapntemplate.u_classNotImplemented class='.$className);
			}
			
			$child = new $className($body);
			$child->tag = $tag;
			$child->props = $propStr;
			$tree->addChild($child);
			
			
			// 递归标签里边的内容
			$this->buildCompiledTree($child);
			
			$content = mb_substr($content, $endTagPos + strlen($endTag) + $this->_rightSpliterLen);
		}
	}
}

abstract class HapTemplate_Tag
{
	var $type = HapTemplate::TYPE_TAG;
	protected $_content;
	var $tag;
	var $level = 0;
	var $props;
	
	var $parent;
	protected $children = array();
	private static $_tag_id = 10000;
	private $id;
	const LOCAL_VARIABLE_PREFIX = '___';
	
	protected $localVars = array();
	
	function __construct($content = NULL)
	{
		$this->_content = $content;
		
		self::$_tag_id++;
		$this->id = self::$_tag_id;
	}
	
	function getContent()
	{
		return $this->_content;
	}
	
	/**
	 * 增加子节点
	 * @param HapTemplate_Tag $child
	 */
	function addChild(HapTemplate_Tag $child)
	{
		//$child->parent = $this;
		$child->level = $this->level + 1;
		
		if (!empty($this->localVars)) {
			foreach($this->localVars as $key => $value) {
				$child->addLocalVariables($key);
			}
		}
		
		$this->children[] = $child;
	}
	
	function __toString()
	{
		$ret = array();
		foreach($this->children as $child) {
			$ret[] = $child->__toString();
		}
		return implode('', $ret);
	}
	
	/**
	 * 添加局部变量
	 * @param string $name
	 */
	public function addLocalVariables($name)
	{
		$this->localVars[$name] = TRUE;
	}
	
	final function getParams($origin = NULL)
	{
		if ($origin === NULL) {
			$origin = trim($this->props);
		} else {
			$origin = trim($origin);
		}
		
		$pos = strpos($origin, '=');
		$params = array();
		
		while($pos) { // break while $pos=0 or $pos=false
			$key = trim(substr($origin, 0, $pos));
			if (!$key) {
				$origin = trim(substr($origin, $pos + 1));
				if (!$origin) {
					break;
				}
				$pos = strpos($origin, '=');
				continue;
			}
			$origin = trim(substr($origin, $pos + 1));
			$value = '';
			if ( $origin[0] == '"' || $origin[0] == '\'' ) {
				$rightPos = strpos($origin, $origin[0], 1);
				if ($rightPos !== FALSE) {
					$value = trim(substr($origin, 1, $rightPos - 1));
					$origin = trim(substr($origin, $rightPos + 1));
				}
			}
			
			if (!$value) {
				$rightPos = strpos($origin, ' ', 1);
				if ($rightPos !== FALSE) {
					$value = trim(substr($origin, 0, $rightPos));
					$origin = trim(substr($origin, $rightPos + 1));
				} else {
					$value = substr($origin, 0);
					$origin = '';
				}
			}
			
			$params[$key] = $value;
			
			if ($origin) {
				$pos = strpos($origin, '=');
			}
		}
		return $params;
	}
	
	/**
	 * 获取变量表达式
	 * @param string $str
	 * @param boolean $thinkLocal 是否考虑局部变量
	 * @return string
	 */
	public function getVariableExp($str, $thinkLocal = TRUE)
	{
		if ($str && $str[0] == '$') {
			$str = substr($str, 1);
		}
		if (!$str) {
			throw new Exception('haptemplate_tag_variable.u_expIsEmpty');
		}
		$arr = array_diff(explode('.', $str), array(''));
		
		// 查找局部变量
		if ($thinkLocal && isset($this->localVars[$arr[0]])) {
			$var = '$'.self::LOCAL_VARIABLE_PREFIX.array_shift($arr);
		} else {
			$var = '$this->v';
		}
		
		foreach($arr as $key) {
			$var .= '[\''.$key.'\']';
		}
		return $var;
	}
}

final class HapTemplate_Tag_Image extends HapTemplate_Tag
{
	function __toString()
	{
		$params = $this->getParams();
		
		//return '<input type="hidden" name="'.$params['id'].'"/><span id="'.$params['id'].'"></span>';
		return $this->_content;
	}
}

/**
 * 文本
 */
final class HapTemplate_Tag_Text extends HapTemplate_Tag
{
	function __toString()
	{
		$params = $this->getParams();
		
		return '<input type="text" name="'.$params['id'].'"/>';
	}
}


/**
 * 根节点
 */
final class HapTemplate_Tag_Root extends HapTemplate_Tag
{
	var $type = HapTemplate::TYPE_ROOT;
}

/**
 * 变量
 * @example
 * <code>
 * {$user.avatar} 或者 {$_city_id}
 * </code>
 */
final class HapTemplate_Tag_Variable extends HapTemplate_Tag
{
	var $type = HapTemplate::TYPE_VARIABLE;
	
	function __toString()
	{
		return '<?php echo '.$this->getVariableExp($this->props).'?>';
	}
	
}
/**
 * 变量为空的标签
 * @example
 * <code>
 * {empty $user.avatar}/static/img/default/user.png{/empty}
 * </code>
 */
class HapTemplate_Tag_Empty extends HapTemplate_Tag
{
	protected $empty = TRUE;
	function __toString()
	{
		if (!count($this->children)) {
			return '';
		}
		if (!$this->props) {
			throw new Exception('haptemplate_tag_empty.u_variableNotSupported');
		}
		$var = $this->getVariableExp($this->props);
		
		$ret = array();
		$ret[] = '<?php if('.($this->empty ? '' : '!').' empty('.$var.') ):?>';
		foreach($this->children as $key => $child) {
			$ret[] = $child->__toString();
		}
		$ret[] = '<?php endif?>';
		return implode('', $ret);
	}
}

/**
 * 变量不为空的标签
 * @example
 * <code>
 * {notempty $user.avatar}{$user.avatar}{/notempty}
 * </code>
 */
final class HapTemplate_Tag_Notempty extends HapTemplate_Tag_Empty
{
	protected $empty = FALSE;
}

/**
 * 循环标签
 * @example
 * <code>
 * <ul>
 * {cycle source="$hotTopics" item="topic}
 * 	<li><a href="/bbs/topic/{$topic.topic_id}">{$topic.topic_title}</a></li>
 * {/cycle}
 * </ul>
 * </code>
 */
final class HapTemplate_Tag_Cycle extends HapTemplate_Tag
{
	private $params = NULL;
	const COUNTER_NAME = 'counter';
	
	function addChild(HapTemplate_Tag $child)
	{
		$this->loadParams();
		
		parent::addChild($child);
	}
	
	function __toString()
	{
		$this->loadParams();
		
		$ret = array();
		$counterName = self::LOCAL_VARIABLE_PREFIX.self::COUNTER_NAME;
		$ret[] = '<?php $'.self::LOCAL_VARIABLE_PREFIX.$this->params['counter'].' = 0;';
		$ret[] = 'foreach('.$this->params['source'].' as $'.$this->params['key'].' => $'.self::LOCAL_VARIABLE_PREFIX.$this->params['item'].'):';
		$ret[] = '$'.self::LOCAL_VARIABLE_PREFIX.$this->params['counter'].'++;?>';
		foreach($this->children as $child) {
			$ret[] = $child->__toString();
		}
		$ret[] = '<?php endforeach?>';
		return implode('', $ret);
	}
	
	private function loadParams()
	{
		if ($this->params === NULL) {
			$this->params = $this->getParams();
			if (!isset($this->params['source']) || !isset($this->params['item'])) {
				throw new Exception('haptemplate_tag_cycle.u_sourceIsNotSupplied');
			}
			
			$this->params['item'] = empty($this->params['item']) ? 'item' : $this->params['item'];
			$this->params['key'] = empty($this->params['key']) ? 'key' : $this->params['key'];
			$this->params['counter'] = empty($this->params['counter']) ? 'counter' : $this->params['counter']; 
			
			$this->addLocalVariables($this->params['item']);
			$this->addLocalVariables($this->params['key']);
			$this->addLocalVariables($this->params['counter']);
		}
	}
}

final class HapTemplate_Tag_Switch extends HapTemplate_Tag
{
	function addChild(HapTemplate_Tag $child)
	{
		// 检查子节点类型，剔除掉非法的节点
		if ($child->tag != 'case' && $child->tag != 'default') {
			return;
		}
		parent::addChild($child);
	}
	
	function __toString()
	{
		$var = $this->getVariableExp($this->props);
		$ret[] = '<?php switch('.$var.'):?>';
		
		$defaultChild = false;
		
		foreach($this->children as $child) {
			if ($child->tag == 'default') {
				$defaultChild = $child;
				continue;
			}
			$ret[] = '<?php case '.$child->getCaseValue().':?>';
			$ret[] = $child->__toString();
			$ret[] = '<?php break?>';
		}
		
		if ($defaultChild) {
			$ret[] = '<?php default:?>';
			$ret[] = $child->__toString();
		}
		
		$ret[] = '<?php endswitch?>';
		return implode('', $ret);
	}
}

final class HapTemplate_Tag_Switch_Case extends HapTemplate_Tag
{
	var $type = HapTemplate::TYPE_SUB_TAG;
	function getCaseValue()
	{
		return $this->props;
	}
}

final class HapTemplate_Tag_Switch_Default extends HapTemplate_Tag
{
	var $type = HapTemplate::TYPE_SUB_TAG;
}

final class HapTemplate_Tag_Include extends HapTemplate_Tag
{
	function __toString()
	{
		$path = parent::__toString();
		return '<?php echo $this->Partial(\''.$path.'\')?>';
	}
}